package com.mars.module.admin.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mars.common.base.UserContextInfo;
import com.mars.common.enums.ProStatusEnums;
import com.mars.common.response.PageInfo;
import com.mars.common.util.DateUtils;
import com.mars.framework.context.ContextUserInfoThreadHolder;
import com.mars.framework.exception.ServiceException;
import com.mars.module.admin.request.SysNotifyRequest;
import com.mars.module.admin.service.ISysNotifyService;
import com.mars.module.system.entity.BaseEntity;
import com.mars.module.system.service.ISysUserService;
import com.mars.module.workflow.entity.ProcessNodeAssignee;
import com.mars.module.workflow.mapper.WorkFlowMapper;
import com.mars.module.workflow.request.ApproveRequest;
import com.mars.module.workflow.request.StartRequest;
import com.mars.module.workflow.response.ProcessResponse;
import com.mars.module.workflow.response.StartResponse;
import com.mars.module.workflow.response.ToDoTaskResponse;
import com.mars.module.workflow.service.IProStartApproveService;
import com.mars.module.workflow.service.IProcessNodeAssigneeService;
import com.mars.module.workflow.service.IProcessNodeService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import com.mars.module.admin.request.BizLeaveRequest;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import com.mars.module.admin.mapper.BizLeaveMapper;
import org.springframework.beans.BeanUtils;
import com.mars.module.admin.entity.BizLeave;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mars.module.admin.service.IBizLeaveService;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 请假业务层处理
 *
 * @author mars
 * @date 2024-01-25
 */
@Slf4j
@Service
@AllArgsConstructor
public class BizLeaveServiceImpl implements IBizLeaveService {

    private final BizLeaveMapper baseMapper;

    private final WorkFlowMapper workFlowMapper;

    private final IProStartApproveService proStartApproveService;

    private final IProcessNodeAssigneeService processNodeAssigneeService;

    private final IProcessNodeService processNodeService;

    private final ISysUserService sysUserService;

    private final ISysNotifyService sysNotifyService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public BizLeave add(BizLeaveRequest request) {
        BizLeave entity = BizLeave.builder().build();
        BeanUtils.copyProperties(request, entity);
        // 设置申请人
        entity.setProStatus(ProStatusEnums.NOT_SUBMITTED.getStatus());
        UserContextInfo userContextInfo = ContextUserInfoThreadHolder.get();
        entity.setApplyUser(userContextInfo.getUserName());
        entity.setApplyTime(LocalDateTime.now());
        entity.setTotalTime(DateUtils.calculateDuration(request.getStartTime(), request.getEndTime(), false));
        baseMapper.insert(entity);
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean delete(Long id) {
        return baseMapper.deleteById(id) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteBatch(List<Long> ids) {
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean update(BizLeaveRequest request) {
        BizLeave entity = BizLeave.builder().build();
        entity.setTotalTime(DateUtils.calculateDuration(request.getStartTime(), request.getEndTime(), false));
        BeanUtils.copyProperties(request, entity);
        return baseMapper.updateById(entity) > 0;
    }

    @Override
    public BizLeave getById(Long id) {
        return baseMapper.selectById(id);
    }

    @Override
    public PageInfo<BizLeave> pageList(BizLeaveRequest request) {
        UserContextInfo userInfo = ContextUserInfoThreadHolder.get();
        Page<BizLeave> page = new Page<>(request.getPageNo(), request.getPageSize());
        Long userId = userInfo.getId();
        if (request.getBusinessType() == 1) {
            return getBizLeavePageInfo(request, page);
        } else if (request.getBusinessType() == 2) {
            return getBizLeaveTodoPageInfo(request, page, userId);
        } else {
            LambdaQueryWrapper<BizLeave> query = this.buildWrapper(request);
            IPage<BizLeave> pageRecord = baseMapper.selectPage(page, query);
            return PageInfo.build(pageRecord);
        }
    }

    @NotNull
    private PageInfo<BizLeave> getBizLeavePageInfo(BizLeaveRequest request, Page<BizLeave> page) {
        LambdaQueryWrapper<BizLeave> query = this.buildWrapper(request);
        IPage<BizLeave> pageRecord = baseMapper.selectPage(page, query);
        List<BizLeave> records = pageRecord.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return new PageInfo<>();
        }
        List<String> proInstanceIds = records.stream().map(BizLeave::getProInstanceId).collect(Collectors.toList());
        List<ToDoTaskResponse> toDoTaskResponses = workFlowMapper.selectTodoTaskListByInstanceId(proInstanceIds);
        Map<String, ToDoTaskResponse> taskResponseMap = toDoTaskResponses.stream().collect(Collectors.toMap(ToDoTaskResponse::getProInstanceId, Function.identity()));
        List<BizLeave> list = records.stream().peek(x -> {
            if (StringUtils.isNotEmpty(x.getProInstanceId())) {
                ToDoTaskResponse toDoTaskResponse = taskResponseMap.get(x.getProInstanceId());
                if (Objects.nonNull(toDoTaskResponse)) {
                    x.setTaskName(toDoTaskResponse.getTaskName());
                } else {
                    if (Objects.nonNull(x.getProStatus())) {
                        if (x.getProStatus() == 3) {
                            x.setTaskName("已完成");
                        } else if (x.getProStatus() == 2) {
                            x.setTaskName("流程中");
                        } else {
                            x.setTaskName("未启动");
                        }
                    } else {
                        x.setTaskName("未启动");
                    }

                }
            }
        }).collect(Collectors.toList());
        pageRecord.setRecords(list);
        return PageInfo.build(pageRecord);
    }

    @NotNull
    private PageInfo<BizLeave> getBizLeaveTodoPageInfo(BizLeaveRequest request, Page<BizLeave> page, Long userId) {
        // 根据用户ID查询代办
        List<ToDoTaskResponse> toDoTaskResponses = workFlowMapper.selectTodoTaskListByAssignee(String.valueOf(userId));
        if (CollectionUtils.isEmpty(toDoTaskResponses)) {
            return new PageInfo<>();
        }
        Map<String, ToDoTaskResponse> taskResponseMap = toDoTaskResponses.stream().collect(Collectors.toMap(ToDoTaskResponse::getBusinessKey, Function.identity(), (entity1, entity2) -> entity1));
        List<Long> businessIds = toDoTaskResponses.stream().map(ToDoTaskResponse::getBusinessKey).collect(Collectors.toList()).stream().map(Long::valueOf).collect(Collectors.toList());
        LambdaQueryWrapper<BizLeave> query = this.buildTodoWrapper(request, businessIds);
        IPage<BizLeave> pageRecord = baseMapper.selectPage(page, query);
        List<BizLeave> records = pageRecord.getRecords();
        if (CollectionUtils.isNotEmpty(records)) {
            List<BizLeave> list = records.stream().peek(x -> {
                ToDoTaskResponse toDoTaskResponse = taskResponseMap.get(String.valueOf(x.getId()));
                BeanUtils.copyProperties(toDoTaskResponse, x);
            }).collect(Collectors.toList());
            pageRecord.setRecords(list);
        }
        return PageInfo.build(pageRecord);
    }

    private LambdaQueryWrapper<BizLeave> buildTodoWrapper(BizLeaveRequest param, List<Long> ids) {
        LambdaQueryWrapper<BizLeave> query = new LambdaQueryWrapper<>();
        query.in(BizLeave::getId, ids);
        if (StringUtils.isNotBlank(param.getType())) {
            query.like(BizLeave::getType, param.getType());
        }
        if (StringUtils.isNotBlank(param.getTitle())) {
            query.like(BizLeave::getTitle, param.getTitle());
        }
        if (StringUtils.isNotBlank(param.getReason())) {
            query.like(BizLeave::getReason, param.getReason());
        }
        if (param.getStartTime() != null) {
            query.like(BizLeave::getStartTime, param.getStartTime());
        }
        if (param.getEndTime() != null) {
            query.like(BizLeave::getEndTime, param.getEndTime());
        }
        query.orderByDesc(BaseEntity::getCreateTime);
        return query;
    }


    @Override
    public List<BizLeave> list(BizLeaveRequest request) {
        LambdaQueryWrapper<BizLeave> query = this.buildWrapper(request);
        return baseMapper.selectList(query);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitProcess(StartRequest request) {
        // 查询请假表单
        BizLeave bizLeave = baseMapper.selectById(request.getBusinessKey());
        if (Objects.isNull(bizLeave)) {
            throw new ServiceException("当前单据不存在");
        }
        // 校验流程参数
        this.checkProcessParams(request);
        // 根据流程key查询审批人员
        List<ProcessNodeAssignee> nodeAssignees = processNodeAssigneeService.getNodeAssigneeList(request.getFlowKey());
        if (CollectionUtils.isEmpty(nodeAssignees)) {
            throw new ServiceException("审批人员未配置");
        }
        if (nodeAssignees.stream().noneMatch(x -> "act_01".equals(x.getNodeId()))) {
            throw new ServiceException("流程审批节点act_01未配置");
        }
        List<ProcessNodeAssignee> assigneeList = nodeAssignees.stream().filter(x -> "act_01".equals(x.getNodeId())).collect(Collectors.toList());
        HashMap<String, Object> map = new HashMap<>(16);
        ProcessNodeAssignee processNodeAssignee = assigneeList.get(0);
        map.put(processNodeAssignee.getAssigneeVariable(), processNodeAssignee.getAssignee());
        map.put("day", Integer.parseInt(DateUtils.calculateDuration(bizLeave.getStartTime(), bizLeave.getEndTime(), true)));
        request.setVariables(map);
        // 发起流程
        StartResponse startResponse = proStartApproveService.startProcess(request);
        bizLeave.setProInstanceId(startResponse.getProcInstId().toString());
        bizLeave.setProStatus(ProStatusEnums.PROCESSING.getStatus());
        baseMapper.updateById(bizLeave);
        // 发送代办消息
        this.sendTodoTaskMsg(Collections.singletonList(Long.valueOf(processNodeAssignee.getAssignee())), "请假审批表单", 2);
    }

    private void sendTodoTaskMsg(List<Long> toUserIds, String message, Integer type) {
        String titlePrefix = type == 1 ? "【通知】:" : "【代办】:";
        SysNotifyRequest build = SysNotifyRequest.builder()
                .toUserIds(toUserIds)
                .content(message)
                .remark(message)
                .type(2)
                .title(titlePrefix + message).build();
        sysNotifyService.sendNotify(build);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitApprove(ApproveRequest request) {
        BizLeave bizLeave = baseMapper.selectById(Long.valueOf(request.getBusinessKey()));
        if (Objects.isNull(bizLeave)) {
            throw new ServiceException("业务单据有误");
        }
        UserContextInfo info = ContextUserInfoThreadHolder.get();
        // 根据当前任务ID获取下一节点ID
        FlowElement flowElement = processNodeService.queryNextStepInfo(request.getTaskId());
        String nextNodeId = Objects.nonNull(flowElement) ? flowElement.getId() : "";
        String flowKey = workFlowMapper.selectProcessKeyByInstanceId(request.getInstanceId());
        // 根据流程key查询审批人员
        List<ProcessNodeAssignee> nodeAssignees = processNodeAssigneeService.getNodeAssigneeList(flowKey);
        if (CollectionUtils.isEmpty(nodeAssignees)) {
            throw new ServiceException("审批人员未配置");
        }
        List<ProcessNodeAssignee> assigneeList = nodeAssignees.stream().filter(x -> x.getNodeId().equals(nextNodeId)).collect(Collectors.toList());
        HashMap<String, Object> map = new HashMap<>(16);
        ProcessNodeAssignee processNodeAssignee = null;
        if (CollectionUtils.isNotEmpty(assigneeList)) {
            processNodeAssignee = assigneeList.get(0);
            map.put(processNodeAssignee.getAssigneeVariable(), processNodeAssignee.getAssignee());
            request.setVariables(map);
        }
        request.setApplyUser(info.getId().toString());
        // 提交完成审批
        boolean hasTask = proStartApproveService.submitApprove(request);
        if (!hasTask) {
            // 更新业务单据完成任务
            bizLeave.setProStatus(ProStatusEnums.FINISHED.getStatus());
            baseMapper.updateById(bizLeave);
            this.sendTodoTaskMsg(Collections.singletonList(bizLeave.getCreateById()), "请假表单审批已完成", 1);
        }
        if (Objects.isNull(processNodeAssignee)) {
            return;
        }
        this.sendTodoTaskMsg(Collections.singletonList(Long.valueOf(processNodeAssignee.getAssignee())), "请假表单", 2);

    }

    /**
     * 校验流程参数
     *
     * @param request request
     */
    private void checkProcessParams(StartRequest request) {
        UserContextInfo contextInfo = ContextUserInfoThreadHolder.get();
        boolean isAdmin = sysUserService.isAdmin(contextInfo.getId());
        if (isAdmin) {
            throw new ServiceException("管理员不允许提交审批");
        }
        List<ProcessResponse> processVo = workFlowMapper.selectProcessByBusinessKey(request.getBusinessKey());
        if (processVo.size() > 0) {
            log.info("重复提交请求：{}", JSONObject.toJSONString(request));
            throw new ServiceException("该流程已提交，请勿重复提交");
        }
        request.setApplyUser(String.valueOf(contextInfo.getUserName()));
    }

    private LambdaQueryWrapper<BizLeave> buildWrapper(BizLeaveRequest param) {
        UserContextInfo userContextInfo = ContextUserInfoThreadHolder.get();
        LambdaQueryWrapper<BizLeave> query = new LambdaQueryWrapper<>();
        query.eq(BaseEntity::getCreateById, userContextInfo.getId());
        if (StringUtils.isNotBlank(param.getType())) {
            query.like(BizLeave::getType, param.getType());
        }
        if (StringUtils.isNotBlank(param.getTitle())) {
            query.like(BizLeave::getTitle, param.getTitle());
        }
        if (StringUtils.isNotBlank(param.getReason())) {
            query.like(BizLeave::getReason, param.getReason());
        }
        if (param.getStartTime() != null) {
            query.like(BizLeave::getStartTime, param.getStartTime());
        }
        if (param.getEndTime() != null) {
            query.like(BizLeave::getEndTime, param.getEndTime());
        }
        query.orderByDesc(BaseEntity::getCreateTime);

        return query;
    }

}
